package org.dawn.deveco.plugin.resourceeditor.utils;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Level;

public final class Logger /*extends com.intellij.openapi.diagnostic.Logger*/ {
    private static boolean useJBLogger = false;
    private String tag = "";

    private Class<?> jbLoggerClass;
    private Object jbLogger;

    public static Logger getInstance(Class clazz) {
        return getInstance(clazz.getSimpleName());
    }

    public static Logger getInstance(String tag) {
        Logger instance = new Logger();
        instance.tag = tag;
        instance.useJBLogger();
        return instance;
    }

    /**
     * Use com.intellij.openapi.diagnostic.Logger
     */
    public void useJBLogger() {
        try {
            this.jbLoggerClass = getClass().getClassLoader().loadClass("com.intellij.openapi.diagnostic.Logger");
            Method builderMethod = this.jbLoggerClass.getMethod("getInstance", String.class);
            this.jbLogger = builderMethod.invoke(null, "[ResourceEditorPlugin]~" + this.tag);
            useJBLogger = true;
        } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |
                 InvocationTargetException e) {
            this.error(e);
        }
    }

    private Object callJBLogger(String method, Object... params) {
        if (this.jbLogger == null) {
            throw new IllegalStateException("JBLogger is not initialized");
        }
        Class<?>[] paramClasses = new Class<?>[params.length];
        for (int i = 0; i < params.length; i++) {
            paramClasses[i] = params[i].getClass();
        }
        try {
            Method m = this.jbLoggerClass.getMethod(method, paramClasses);
            return m.invoke(this.jbLogger, params);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            System.err.println("===== Failed to call a method of com.intellij.openapi.diagnostic.Logger");

            StringBuffer sb = new StringBuffer();
            sb.append("[").append(this.tag).append("]");
            sb.append(" ").append(getLogLevelStr(method));
            sb.append(" - ");

            int exceptionIndex = -1;
            for (int i = 0; i < params.length; i++) {
                if (params[i] instanceof Throwable) {
                    exceptionIndex = i;
                    break;
                }
                sb.append(params[i]);
                sb.append("\n");
            }

            if (exceptionIndex >= 0) {
                sb.append(dumpThrowable((Throwable) params[exceptionIndex]));
            }

            System.err.println(sb);
            return null;
        }
    }

    private String getLogLevelStr(String method) {
        String ret = "UNKNOWN";
        if (method == null)
            return ret;
        if (method.startsWith("debug"))
            ret = "DEBUG";
        else if (method.startsWith("info"))
            ret = "INFO";
        else if (method.startsWith("warn"))
            ret = "WARN";
        else if (method.startsWith("error"))
            ret = "ERROR";
        return ret;
    }

    private String dumpThrowable(Throwable t) {
        if (t == null) {
            return "";
        }
        StringWriter sw = new StringWriter(1024);
        PrintWriter out = new PrintWriter(sw);
        t.printStackTrace(out);
        return sw.toString();
    }

    public boolean isDebugEnabled() {
        if (useJBLogger) {
            Object ret = callJBLogger("isDebugEnabled");
            return ret != null && (boolean) ret;
        }
        return false;
    }

    public void debug(String message) {
        if (useJBLogger) {
            callJBLogger("debug", message);
            return;
        }
        System.out.printf("[%s] DEBUG - %s%n", this.tag, message);
    }

    public void debug(@Nullable Throwable t) {
        if (useJBLogger) {
            callJBLogger("debug", t);
            return;
        }
        System.out.printf("[%s] DEBUG - %s%n", this.tag, dumpThrowable(t));
    }

    public void debug(String category, String message) {
        if (useJBLogger) {
            callJBLogger("debug", String.format("[%s] %s", category, message));
            return;
        }
        System.out.printf("[%s] DEBUG - [%s] %s%n", this.tag, category, message);
    }

    public void debugF(String category, String format, Object... args) {
        debug(category, String.format(format, args));
    }

    public void debug(String message, @Nullable Throwable t) {
        if (useJBLogger) {
            callJBLogger("debug", message, t);
            return;
        }
        System.out.printf("[%s] DEBUG - %s%n", this.tag, message);
        if (t != null) {
            System.out.printf("[%s] DEBUG - %s%n", this.tag, dumpThrowable(t));
        }
    }

    public void info(@NotNull Throwable t) {
        if (useJBLogger) {
            callJBLogger("info", t);
            return;
        }
        System.out.printf("[%s] INFO - %s%n", this.tag, dumpThrowable(t));
    }

    public void info(String category, String message) {
        if (useJBLogger) {
            callJBLogger("info", String.format("[%s] %s", category, message));
            return;
        }
        System.out.printf("[%s] INFO - [%s] %s%n", this.tag, category, message);
    }

    public void info(String message) {
        if (useJBLogger) {
            callJBLogger("info", message);
            return;
        }
        System.out.printf("[%s] INFO - %s%n", this.tag, message);
    }

    public void info(String message, @Nullable Throwable t) {
        if (useJBLogger) {
            callJBLogger("info", message, t);
            return;
        }
        System.out.printf("[%s] INFO - %s%n", this.tag, message);
        if (t != null) {
            System.out.printf("[%s] INFO - %s%n", this.tag, dumpThrowable(t));
        }
    }

    public void infoF(String category, String format, Object... args) {
        info(category, String.format(format, args));
    }

    public void warn(String message) {
        if (useJBLogger) {
            callJBLogger("warn", message);
            return;
        }
        System.out.printf("[%s] WARN - %s%n", this.tag, message);
    }

    public void warn(String category, String message) {
        if (useJBLogger) {
            callJBLogger("warn", String.format("[%s] %s", category, message));
            return;
        }
        System.out.printf("[%s] WARN - [%s] %s%n", this.tag, category, message);
    }

    public void warnF(String category, String format, Object... args) {
        warn(category, String.format(format, args));
    }

    public void warn(String message, @Nullable Throwable t) {
        if (useJBLogger) {
            callJBLogger("warn", message, t);
            return;
        }
        System.out.printf("[%s] WARN - %s%n", this.tag, message);
        if (t != null) {
            System.out.printf("[%s] WARN - %s%n", this.tag, dumpThrowable(t));
        }
    }

    public void warn(Throwable t) {
        if (useJBLogger) {
            callJBLogger("warn", t);
            return;
        }
        System.out.printf("[%s] WARN - %s%n", this.tag, dumpThrowable(t));
    }

    public void error(String message) {
        if (useJBLogger) {
            callJBLogger("error", message);
            return;
        }
        System.out.printf("[%s] ERROR - %s%n", this.tag, message);
    }

    public void error(String message, @Nullable Throwable t) {
        if (useJBLogger) {
            callJBLogger("error", message, t);
            return;
        }
        System.out.printf("[%s] ERROR - %s%n", this.tag, message);
        if (t != null) {
            System.out.printf("[%s] ERROR - %s%n", this.tag, dumpThrowable(t));
        }
    }

    public void error(Throwable t) {
        if (useJBLogger) {
            callJBLogger("error", t);
            return;
        }
        System.out.printf("[%s] ERROR - %s%n", this.tag, dumpThrowable(t));
    }

    public void error(String message, @Nullable Throwable t, String @NotNull ... details) {
        if (useJBLogger) {
            callJBLogger("error", message, t, details);
            return;
        }
        System.out.printf("[%s] ERROR - %s%n", this.tag, message);
        if (details.length > 0) {
            System.out.printf("[%s] ERROR - details: %s%n", this.tag, String.join("\n", details));
        }
        if (t != null) {
            System.out.printf("[%s] ERROR - %s%n", this.tag, dumpThrowable(t));
        }
    }

    @Deprecated
    public void setLevel(@NotNull Level level) {
        if (useJBLogger) {
            callJBLogger("setLevel", level);
        }
    }
}
